/*
文件配置源：实现从文件系统加载和保存配置的功能，支持单个文件和目录（递归加载目录下所有文件），

	并实现了config.ConfigSource接口，作为配置管理器的文件类型数据源。
*/
package filesource

import (
	"context"                // 提供上下文功能，用于控制操作的生命周期（如超时、取消）
	"gameServer/config"      // 包含配置相关的接口定义（如ConfigSource）和结构体（如Config）
	"gameServer/errors"      // 自定义错误类型，用于返回配置操作中的错误（如无权限、路径无效）
	"gameServer/utils/rfile" // 文件操作工具，提供文件写入等功能
	"io"                     // 提供I/O操作基础接口（如ReadAll）
	"io/fs"                  // 文件系统接口，用于目录遍历（如DirEntry）
	"os"                     // 提供操作系统级别的文件操作（如Open、Stat）
	"path/filepath"          // 路径处理工具，用于解析、拼接文件路径
	"strings"                // 字符串处理工具，用于处理文件名、路径前缀等
)

// Name 定义文件配置源的唯一标识名称，用于在配置管理器中区分不同类型的配置源
const Name = "filesource"

// FileSource 文件配置源结构体，实现了config.ConfigSource接口
// 负责从指定路径（文件或目录）加载配置，以及根据权限模式保存配置
type FileSource struct {
	path string            // 配置文件或目录的基础路径（加载/保存的根路径）
	mode config.ConfigMode // 配置模式（如只读、只写、读写），控制是否允许保存操作
}

// 配置修改函数，修改默认配置
type FixFileSourceFunc func(fls *FileSource)

// 编译时接口实现检查：确保FileSource结构体完全实现了config.ConfigSource接口
// 若未实现接口的所有方法，编译时会报错，避免运行时错误
var _ config.ConfigSource = &FileSource{}

const (
	defaultPath = "./config"
	defaultMode = config.ReadOnly
)

const (
	defaultPathKey = "etc.config.filesource.path"
	defaultModeKey = "etc.config.filesource.mode"
)

// NewFileSource 创建一个新的文件配置源实例
// 参数：
//
//	path: 配置文件或目录的基础路径（后续加载/保存操作都基于此路径）
//	mode: 配置模式（config.ConfigMode），限制操作权限（如Readonly模式下不允许保存）
//
// 返回值：
//
//	*FileSource: 初始化后的文件配置源实例
func NewFileSource(path string, mode config.ConfigMode) *FileSource {
	fls := &FileSource{
		path: path,

		mode: mode,
	}

	return fls
}

// 设置配置文件或配置目录路径
func SetPath(path string) FixFileSourceFunc {
	return func(fls *FileSource) { fls.path = path }
}

// 设置读写模式
func SetMode(mode config.ConfigMode) FixFileSourceFunc {
	return func(fls *FileSource) { fls.mode = mode }
}

// Name 实现config.ConfigSource接口的Name方法，返回配置源的唯一标识名称
// 返回值：
//
//	string: 固定返回常量Name（"filesource"），用于标识当前是文件类型的配置源
func (fls *FileSource) Name() string {
	return Name
}

// GetPath 返回配置源的路径
func (fls *FileSource) GetPath() string {
	return fls.path
}

// Load 实现config.ConfigSource接口的Load方法，加载配置文件或目录下的所有配置
// 功能：根据传入的路径（优先使用file参数，其次使用fls.path）加载配置，支持单个文件或目录（递归加载目录内所有文件）
// 参数：
//
//	ctx: 上下文，可用于控制加载操作的超时或取消
//	filesource: 可选参数，指定要加载的子文件/子目录（相对于fls.path），若为空则加载fls.path本身
//
// 返回值：
//
//	[]*config.Config: 加载到的配置列表（每个元素对应一个文件的配置信息）
//	error: 加载过程中发生的错误（如路径不存在、权限不足等）
func (fls *FileSource) Load(ctx context.Context, file ...string) ([]*config.Config, error) {
	// 确定最终加载的路径：优先使用file参数（若不为空），否则使用基础路径fls.path
	path := fls.path
	if len(file) > 0 && file[0] != "" {
		// 拼接基础路径和文件参数
		path = filepath.Join(fls.path, file[0])
	}

	// 检查最终路径的状态（是否存在、是文件还是目录）
	info, err := os.Stat(path)
	if err != nil {
		return nil, err // 路径不存在或无法访问，返回错误
	}

	// 根据路径类型（目录/文件）执行不同的加载逻辑
	if info.IsDir() {
		// 路径是目录，调用loadDir加载目录下所有文件
		return fls.loadDir(path)
	} else {
		// 路径是单个文件，调用loadFile加载该文件
		cf, err := fls.loadFile(path)
		if err != nil {
			return nil, err
		}
		return []*config.Config{cf}, nil // 包装为切片返回（单个文件的配置）
	}
}

// loadFile 加载单个配置文件的内容，解析文件信息并封装为config.Config结构体
// 参数：
//
//	path: 单个文件的完整路径
//
// 返回值：
//
//	*config.Config: 包含文件路径、名称、格式、内容等信息的配置结构体
//	error: 加载过程中发生的错误（如文件无法打开、读取失败等）
func (fls *FileSource) loadFile(path string) (*config.Config, error) {
	// 打开文件（只读模式）
	file, err := os.Open(path)
	if err != nil {
		return nil, err // 文件打开失败（如权限不足、文件不存在），返回错误
	}
	defer file.Close() // 延迟关闭文件，确保资源释放

	// 获取文件的元信息（如名称、大小、是否为目录等）
	info, err := file.Stat()
	if err != nil {
		return nil, err // 获取文件信息失败，返回错误
	}

	// 读取文件的全部内容（字节流）
	content, err := io.ReadAll(file)
	if err != nil {
		return nil, err // 读取文件内容失败，返回错误
	}

	// 解析文件扩展名（如".json" → "json"），用于后续解码时识别格式
	ext := filepath.Ext(info.Name())
	// 获取文件的绝对路径（规范化路径，避免相对路径带来的歧义）
	path_1, _ := filepath.Abs(path)
	// 获取基础路径的绝对路径（用于计算相对路径）
	path_2, _ := filepath.Abs(fls.path)
	// 计算文件相对于基础路径的相对路径（去掉基础路径前缀）
	path = strings.TrimPrefix(path_1, path_2)
	// 拼接基础路径和相对路径，得到完整的绝对路径（用于标识文件的唯一位置）
	full_path := fls.path + path

	// 封装为config.Config结构体返回，包含文件的关键信息
	return &config.Config{
		Path:     path,                                 // 文件相对于基础路径的相对路径
		File:     info.Name(),                          // 文件名（含扩展名）
		Name:     strings.TrimSuffix(info.Name(), ext), // 文件名（不含扩展名），作为配置的标识名
		Format:   strings.TrimPrefix(ext, "."),         // 文件格式（如"json"、"yaml"），去掉前缀"."
		Content:  content,                              // 文件的原始内容（字节流），用于后续解码
		FullPath: full_path,                            // 文件的完整绝对路径，用于定位文件
	}, nil
}

// loadDir 加载指定目录下的所有配置文件（递归遍历子目录），并收集为配置列表
// 参数：
//
//	path: 目录的完整路径
//
// 返回值：
//
//	[]*config.Config: 目录下所有文件的配置结构体列表
//	error: 遍历或加载过程中发生的错误（如目录无法访问、文件加载失败等）
func (fls *FileSource) loadDir(path string) (configs []*config.Config, err error) {
	// 使用filepath.WalkDir递归遍历目录下的所有文件和子目录
	// 第一个参数：要遍历的目录路径
	// 第二个参数：遍历回调函数，处理每个遍历到的文件/目录
	err = filepath.WalkDir(path, func(path string, info fs.DirEntry, err error) error {
		// 若遍历过程中发生错误（如子目录无法访问），返回错误终止遍历
		if err != nil {
			return err
		}

		// 过滤不需要处理的路径：
		// 1. 跳过目录（只处理文件）
		// 2. 跳过以"."结尾的文件（通常是临时文件或无效文件）
		if info.IsDir() || strings.HasSuffix(info.Name(), ".") {
			return nil // 继续遍历下一个条目
		}

		// 加载当前文件的配置
		cf, err := fls.loadFile(path)
		if err != nil {
			return err // 文件加载失败，返回错误终止遍历
		}

		// 将加载到的配置添加到列表中
		configs = append(configs, cf)
		return nil // 继续遍历下一个条目
	})
	return // 返回收集到的配置列表和可能的错误
}

// Store 实现config.ConfigSource接口的Save方法，保存配置内容到指定文件
// 功能：根据配置模式检查权限，将内容写入指定文件（基于fls.path的相对路径）
// 参数：
//
//	ctx: 上下文，可用于控制保存操作的超时或取消
//	filesource: 要保存的文件名（相对于fls.path的路径）
//	content: 要写入文件的内容（字节流）
//
// 返回值：
//
//	error: 保存过程中发生的错误（如无权限、路径无效等）
func (fls *FileSource) Store(ctx context.Context, file string, content []byte) error {
	// 检查配置模式是否允许写入操作：只允许WriteOnly（只写）或ReadWrite（读写）模式
	if fls.mode != config.WriteOnly && fls.mode != config.ReadWrite {
		return errors.ErrNoOperationPermission // 无写入权限，返回自定义错误
	}

	// 检查基础路径（fls.path）是否为目录（保存操作需要基于目录路径）
	info, err := os.Stat(fls.path)
	if err != nil {
		return err // 基础路径不存在或无法访问，返回错误
	}
	if !info.IsDir() {
		// 基础路径不是目录（是文件），无法在其下保存文件，返回错误
		return errors.New("the specified filesource cannot be modified under the filesource path")
	}

	// 拼接基础路径和文件名，得到完整的保存路径，调用rfile.WriteFile写入内容
	return rfile.WriteFile(filepath.Join(fls.path, file), content)
}

// 监听配置变化
func (fls *FileSource) Watch(ctx context.Context) (config.ConfigWatcher, error) {
	return newWatcher(ctx, fls)
}

// Close 实现ConfigSource接口的Close方法
func (f *FileSource) Close() error {
	return nil
}